/*
 * logger.c
 *
 *   Created on: 2023-05-15
 *       Author: lidq
 */

#include <logger.hpp>
#include <rtime.hpp>
#include <settings.hpp>
#include <utils.hpp>

#define NAME_SIZE (200)

// 日志等级名称
static const char* LOG_LEVEL_STR[] = { "DBG", "INF", "WAR", "ERR", "CRI" };

void LoggerText(int log_level, const char* fmt, ...)
{
	if (fmt == NULL)
	{
		return;
	}

	if (log_level < LOGLEVEL_DEBUG)
	{
		log_level = LOGLEVEL_DEBUG;
	}
	if (log_level > LOGLEVEL_CRITIAL)
	{
		log_level = LOGLEVEL_CRITIAL;
	}

	int len = strlen(fmt);
	if (len <= 0)
	{
		return;
	}

	char ch = 0;
	if (len >= 1 && fmt[len - 1] != '\n')
	{
		ch = '\n';
	}

	// 生成日志内容头和尾
	char datetime[200]		  = { 0 };
	char line[LOG_TEMP_SIZE]  = { 0 };
	char line2[LOG_LINE_SIZE] = { 0 };
	rt_string(datetime, 200);

	va_list ap;
	va_start(ap, fmt); // 获得可变参数列表
	vsnprintf(line, LOG_TEMP_SIZE, fmt, ap);
	snprintf(line2, LOG_LINE_SIZE, "%s [%s]: %s%c", datetime, LOG_LEVEL_STR[(int)log_level], line, ch);
	va_end(ap); // 释放资源

	// 控制台显示log等级
	if (log_level >= g_Settings->log_console_level)
	{
		printf("%s", line);
	}

	Logger::Instance()->Text(log_level, line2);
}

// 记录文字类日志
void Logger::Text(int log_level, const char* line)
{
	if (!m_Running)
	{
		return;
	}

	// 文件保存log等级
	if (log_level >= g_Settings->log_file_level)
	{
		// 写入日志缓冲区
		m_SemRw.Wait();
		TextPush(line);
		// 发信号保存
		m_SemSig.Post();
		m_SemRw.Post();
	}
}

void Logger::GetLogName(char* out_name, int size)
{
	if (out_name == NULL)
	{
		return;
	}
	rt_string(out_name, size);

	// 只使用日期作为日志文件名，每天会生成一个新文件
	out_name[10] = 0;
}

// 加入新log
void Logger::TextPush(const char* buff)
{
	if (m_LogText == NULL)
	{
		return;
	}

	strncpy(m_LogText->line[m_LogText->head], buff, LOG_LINE_SIZE);
	m_LogText->head++;
	m_LogText->head %= LOG_LINE_CNT;
	if (m_LogText->head == m_LogText->tail)
	{
		m_LogText->tail++;
		m_LogText->tail %= LOG_LINE_CNT;
	}
}

// 取出全部log
int Logger::TextPopAll(char* buff, int maxsize)
{
	if (m_LogText == NULL)
	{
		return 0;
	}

	if (buff == NULL)
	{
		return 0;
	}

	char* p		   = buff;
	int total_size = 0;
	int cnt		   = TextCnt();
	for (int i = 0; i < cnt; i++)
	{
		int len = strlen(m_LogText->line[m_LogText->tail]);
		if (total_size + len > maxsize)
		{
			break;
		}

		strncpy(p, m_LogText->line[m_LogText->tail], LOG_LINE_SIZE);

		total_size += len;
		p += len;

		m_LogText->tail++;
		m_LogText->tail %= LOG_LINE_CNT;
	}

	return total_size;
}

// 当前log数量
int Logger::TextCnt(void)
{
	if (m_LogText == NULL)
	{
		return 0;
	}

	int size = m_LogText->head - m_LogText->tail;
	if (size < 0)
	{
		size += LOG_LINE_CNT;
	}
	return size;
}

void logger_dump_errors(int signo)
{
	LOGC("error signo %d\n", signo);

	void* buffer[512] = { 0 };
	size_t size;
	char** strs = NULL;
	size_t i	= 0;

	size = backtrace(buffer, 512);
	LOGC("Obtained %zd stack frames.nm\n", size);

	strs = backtrace_symbols(buffer, size);
	if (strs == NULL)
	{
		LOGC("backtrace_symbols.\n");
		Logger::Instance()->Save();
		// 等待log保存
		rl_cleanup_after_signal();
		sleep(1);
		exit(EXIT_FAILURE);
	}

	for (i = 0; i < size; i++)
	{
		LOGC("%s\n", strs[i]);
	}
	free(strs);
	strs = NULL;

	Logger::Instance()->Save();

	rl_cleanup_after_signal();
	// 等待log保存
	sleep(1);

	exit(EXIT_SUCCESS);
}

//
static Logger* s_Logger = nullptr;

Logger* Logger::Instance()
{
	if (s_Logger == nullptr)
	{
		s_Logger = new Logger();
	}
	return s_Logger;
}

Logger::Logger()
	: TaskRun()
	, m_Running(false)
	, m_ForceSave(false)
	, m_LogText(nullptr)
	, m_LogBuffText(nullptr)
	, m_SemRw(1)
	, m_SemSig(0)
{
}

Logger::~Logger()
{
}

void Logger::Start(void* arg)
{
	m_Running	= true;
	m_ForceSave = false;

	// 初始化文字类日志buff
	m_LogText		= (log_text_s*)malloc(sizeof(log_text_s));
	m_LogText->head = 0;
	m_LogText->tail = 0;

	// 为每一行buff申请内存
	for (int i = 0; i < LOG_LINE_CNT; i++)
	{
		m_LogText->line[i] = (char*)malloc(LOG_LINE_SIZE);
	}

	// 初始化文字类文件写入buff
	m_LogBuffText = (char*)malloc(LOG_LINE_SIZE * LOG_LINE_CNT);

	// 创建日志存储目录
	char* log_path = GetPrgFilePath(NAME_LOG_FOLDER);
	MkDirPath(log_path);
	free(log_path);

	// 启动logger线程
	TaskRun::Start(arg);

	// 启动强制保存线程
	m_AutoSave.Start(this);
}

void Logger::Run(void* arg)
{
	// 当前日志文件名
	char log_name_curr[NAME_SIZE] = { 0 };
	memset(log_name_curr, 0, NAME_SIZE);

	// 最新应该的文件名
	char log_name_new[NAME_SIZE] = { 0 };
	memset(log_name_new, 0, NAME_SIZE);

	// 临时字符串
	char filepath[NAME_SIZE * 2] = { 0 };
	memset(filepath, 0, NAME_SIZE * 2);

	int fd		  = -1;
	int size_curr = 0;

	while (m_Running)
	{
		// 读取需要写入的日志到buff里
		m_SemSig.Wait();
		m_SemRw.Wait();
		int len = TextPopAll(m_LogBuffText + size_curr, LOG_LINE_SIZE * LOG_LINE_CNT);
		size_curr += len;
		m_SemRw.Post();

		// 如果日志大小超过buff的一半，则写入到文件
		if (size_curr > LOG_LINE_SIZE * LOG_LINE_CNT / 2)
		{
			m_ForceSave = true;
		}

		if (m_ForceSave)
		{
			if (size_curr > 0)
			{
				// 按日期获取文件名
				GetLogName(log_name_new, NAME_SIZE);
				// 如果日期变化了
				if (strcmp(log_name_curr, log_name_new) != 0)
				{
					// 关闭之前文件
					if (fd > 0)
					{
						close(fd);
						fd = -1;
					}
					// 更新文件名
					strncpy(log_name_curr, log_name_new, NAME_SIZE);
				}

				snprintf(filepath, NAME_SIZE * 2, "%s/%s_%s.log", NAME_LOG_FOLDER, NAME_LOG, log_name_curr);
				char* path = GetPrgFilePath(filepath);
				int ret	   = access(path, F_OK);
				// 打开文件，文件名按日期生成
				if (fd < 0 || ret == -1)
				{
					fd = open(path, O_CREAT | O_APPEND | O_WRONLY, 0666);
				}
				free(path);

				// 写入日志
				write(fd, m_LogBuffText, size_curr);
				fsync(fd);
			}

			// 清空buff
			size_curr	= 0;
			m_ForceSave = false;
		}
	}

	// 关闭文件
	if (fd > 0)
	{
		close(fd);
		fd = -1;
	}
}

void Logger::Save(void)
{
	m_ForceSave = true;
	m_SemSig.Post();
}

///////////////////////////////////////////////////////////////////////////////////////////

LoggerAutoSave::LoggerAutoSave()
	: TaskRun()
{
}

LoggerAutoSave::~LoggerAutoSave()
{
}

void LoggerAutoSave::Run(void* arg)
{
	Logger* logger = (Logger*)arg;

	uint32_t tk = 0;
	while (logger->m_Running)
	{
		if (tk % (g_Settings->log_force_save_ms / 100) == 0)
		{
			logger->m_ForceSave = true;
			logger->m_SemSig.Post();
		}
		tk++;
		TaskRun::mSleep(100);
	}
}

/* @RegistMainFunction */
int logger_main(int argc, char** argv)
{
	if (argc == 2 && strcmp(argv[1], "save") == 0)
	{
		Logger::Instance()->Save();
		printf("Logger save to file.\n");
		return 0;
	}
	return 0;
}
